#include <cstdio>
#include <sstream>
#include <sqlite3.h>

#include "Database.h"

int Database::errno = 0;
char* Database::errmsg = (char*) 0;

Database::Database(string &path, string &files_dir) {
	this->db = 0;	
	this->path = path;
	this->files_dir = files_dir;
	this->create_database();
}

int Database :: create_database(){
    char * err_msg;
    errno = 0;
    set_errmsg("");
        
    if (!db) {
	errno = sqlite3_open(path.c_str(), &db);
    }
	
    if (errno) {
        set_errmsg(sqlite3_errmsg(db));
        sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }

    /* baza danych otwara */
    errno = sqlite3_exec(db, "          \
        CREATE TABLE files (            \
            name VARCHAR(255),          \
            location VARCHAR(255),      \
            id INTEGER PRIMARY KEY      \
        );                              \
    ", 0, 0, &err_msg);
    
    if (SQLITE_OK != errno) {
        set_errmsg(sqlite3_errmsg(db));
        sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }
    
    errno = sqlite3_exec(db, "          \
        CREATE TABLE tags (             \
            name VARCHAR(255),          \
            id INTEGER PRIMARY KEY      \
        );                              \
    ", 0,0, &err_msg);
    
    if (SQLITE_OK != errno) {
        set_errmsg(sqlite3_errmsg(db));
        sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }

    errno = sqlite3_exec(db, "          \
        CREATE TABLE file_has_tags (    \
            tags_id INTEGER,            \
            files_id INTEGER            \
        );                              \
    ", 0, 0, &err_msg);
    
    if (SQLITE_OK != errno) {
        set_errmsg(sqlite3_errmsg(db));
        sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }

    return errno==SQLITE_OK?0:-1;
};

void Database::set_errmsg(const char * msg) {
    if (errmsg) {
	free(errmsg);
    }
    
    errmsg = (char *) malloc(strlen(msg) * sizeof(char*));
    
    if (errmsg != NULL) {
	strcpy(errmsg, msg);
    }
}

const char * Database::get_errmsg() {
    return Database::errmsg;
}

int Database::get_errno() {
    return errno;
}


/*****************************************************************************
 *		GET TAGS						     *
 *****************************************************************************/

static
int cb_get_tags(void *cb_arg, int argc, char **argv, char** colnames) {
    hash_map<string, Tag>* tags = (hash_map<string, Tag>*) cb_arg;

    int id = atoi(argv[0]);
    string name = string(argv[1]);

    Tag t(id, name);

    (*tags)[name] = t;
    
    return 0;
}


hash_map<string, Tag>* Database::get_tags() {
    char* err_msg = 0;
    errno = 0;
    set_errmsg("");

    hash_map<string, Tag>* ret_val = new hash_map<string, Tag>();

    if (!db) {
	errno = sqlite3_open(path.c_str(), &db);
    }

    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	delete ret_val;
	ret_val = NULL;
	return ret_val;
    }

    /* baza danych otwara */
    string query = string("SELECT id, name FROM tags");
    errno = sqlite3_exec(db, query.c_str(), cb_get_tags, ret_val, &err_msg);

    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	delete ret_val;
	ret_val = NULL;
    }

    return ret_val;
}

///////////////////////////////
//// temporary ;)

int cb_get_file_tags(void *cb_arg, int argc, char **argv, char** colnames) {
    vector<Tag>* tags = (vector<Tag>*) cb_arg;

    int id = atoi(argv[0]);
    string name = string(argv[1]);

    Tag t(id, name);

    tags->push_back(t);
    
    return 0;
}

vector<Tag>* Database::get_tags(File &file) {
    char * err_msg;
    errno = 0;
    set_errmsg("");

    vector<Tag>* ret_val = new vector<Tag>();
    
    if (!db) {
        errno = sqlite3_open(path.c_str(), &db);
    }

    if (errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	delete ret_val;
	ret_val = NULL;
	return ret_val;
    }

    /* baza danych otwara */
    stringstream query;
    query << "SELECT tags.id, tags.name FROM ";
    query << "tags JOIN (SELECT tags_id, files_id FROM ";
    query << "file_has_tags WHERE files_id = ";
    query << file.get_id();
    query << ") ON tags.id = tags_id";
    errno = sqlite3_exec(db, query.str().c_str(), cb_get_file_tags, 
			    ret_val, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	delete ret_val;
	ret_val = NULL;
    }

    return ret_val;
};

/*****************************************************************************
 *		del (tag)						     *
 *****************************************************************************/
int Database::del(const Tag& tag) {
    char *err_msg = 0;
    errno = 0;
    
    if (!db) {
	errno = sqlite3_open(path.c_str(), &db);
    }
    
    if (errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }
    
    stringstream query;
    query << "DELETE FROM tags WHERE id=" << tag.get_id();
    
    errno = sqlite3_exec(db, query.str().c_str(), 0, 0, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
    }
    
    return errno==SQLITE_OK?0:-1;
}


/*****************************************************************************
 *		insert (tag)						     *
 *****************************************************************************/
static 
int	cb_insert_tag(void* cb_arg, int argc, char **argv, char **colnames) {
    Tag * t = (Tag*) cb_arg;
    
    int id = atoi(argv[0]);
    
    t->set_id(id);

    return 0;
}

int Database::insert(Tag& tag) {
    char *err_msg = 0;
    errno = 0;
    set_errmsg("");
    
    if (!db) {
	errno = sqlite3_open(path.c_str(), &db);
    }
    
    if (errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }
    
    stringstream query;
    query << "INSERT INTO tags(name) VALUES('" << tag.get_name() << "')";
    
    errno = sqlite3_exec(db, query.str().c_str(), 0, 0, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }
    
    stringstream query2;
    query2 << "SELECT id FROM tags WHERE name='" << tag.get_name() << "'";
    errno = sqlite3_exec(db, query2.str().c_str(), cb_insert_tag, &tag, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
    }
    
    return errno==SQLITE_OK?0:-1;
}


/*****************************************************************************
 *		insert (file)						     *
 *****************************************************************************/
static 
int	cb_insert_file(void* cb_arg, int argc, char **argv, char **colnames) {
    File * f = (File*) cb_arg;
    
    int id = atoi(argv[0]);
    
    f->set_id(id);

    return 0;
}

int Database::insert(File& file) {
    char *err_msg = 0;
    errno = 0;
    set_errmsg("");
    
    if (!db) {
	errno = sqlite3_open(path.c_str(), &db);
    }
    
    if (errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }
    
    stringstream query;
    query << "INSERT INTO files(name, location) VALUES('" 
    		<< file.get_name() << "','" << "X" << "')";
    
    errno = sqlite3_exec(db, query.str().c_str(), 0, 0, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }
    
    stringstream query2;
    query2 << "SELECT id FROM files WHERE name='" << file.get_name() << "'";
    errno = sqlite3_exec(db, query2.str().c_str(), cb_insert_file, &file, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return -1;
    }
    
    /* jest plik w bazie danych, tworzymy go na dysku */
    stringstream ofile;
    ofile << this->files_dir << "/" << file.get_id();

    FILE * fp = fopen(ofile.str().c_str(), "w");
    if (fp!=0) {
	fclose(fp);
    } else {
	set_errmsg("File not created");
	return -1;
    }

    return errno==SQLITE_OK?0:-1;
}


/*****************************************************************************
 *		add(file, tag)						     *
 *****************************************************************************/
// wersja niekoniecznie chlujna :-]
static 
int	cb_add_tag(void* cb_arg, int argc, char **argv, char **colnames) {
    Tag * f = (Tag*) cb_arg;
    
    int id = atoi(argv[0]);
    
    f->set_id(id);

    return 0;
}

int Database::add(File &file, Tag &tag) {
    char *err_msg = 0;
    errno = 0;
    set_errmsg("");
    
    if (!db) {
	errno = sqlite3_open(path.c_str(), &db);
    }
    
    if (errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }
   /* TODO: obsluzyc brak taga w bd */ 
    stringstream query;
    query << "SELECT id FROM tags WHERE name='" << tag.get_name() << "'";
    
    errno = sqlite3_exec(db, query.str().c_str(), cb_add_tag, &tag, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return -1;
    }

    stringstream query2;
    query2 << "INSERT INTO file_has_tags(tags_id, files_id) VALUES(" 
    		<< tag.get_id() << "," << file.get_id() << ")";
    
    errno = sqlite3_exec(db, query2.str().c_str(), 0, 0, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }


    return errno==SQLITE_OK?0:-1;
}

/*****************************************************************************
 *		Go ET FILES						     *
 *****************************************************************************/
static
int cb_get_files(void *cb_arg, int argc, char **argv, char** colnames) {
    vector<File>* files = (vector<File>*) cb_arg;

    int id = atoi(argv[0]);
    string name = string(argv[1]);
    string location = string(argv[2]);

    File f(id, name, location);

    files->push_back(f);

    return 0;
}

//inline
File* Database::get_file(const vector<Tag> & v, const char* filename) {
    vector<File>* r = get_files(v, filename);
    if (r->size() == 0) {
	delete r;
	return 0;
    } else{
	File* ret = new File((*r)[0]);
	delete r;
	return ret;
    }
}

vector<File>* Database::get_files(const vector<Tag> & v, const char* filename) {
    char * err_msg;
    errno = 0;
    set_errmsg("");

    vector<File>* ret_val = new vector<File>();

    if (!db) {
	errno = sqlite3_open(path.c_str(), &db);
    }

    if (errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	delete ret_val;
	return 0;
    }

    /* baza danych otwara */
    stringstream query;
    query << "SELECT id, name, location FROM files F WHERE 1=1 ";
    
    vector<Tag>::const_iterator it;
    for(it = v.begin(); it != v.end(); it++) {
	query << " AND EXISTS(SELECT 'x' FROM file_has_tags WHERE files_id=F.id AND tags_id=" << it->get_id() << ")";
    }
    
    if (filename) {
	query << " AND name='" << string(filename) << "' ";
    }
    
    query << " ORDER BY F.name DESC";
    
    errno = sqlite3_exec(db, query.str().c_str(), cb_get_files, ret_val, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	delete ret_val;
	return 0;
    }

    return ret_val;
}

const string& Database::get_files_dir() const{
	return files_dir;
}


int Database::del(File& f, Tag &t) {
    char *err_msg = 0;
    errno = 0;
    set_errmsg("");
    
    if (!db) {
	errno = sqlite3_open(path.c_str(), &db);
    }
    
    if (errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }
    
    stringstream query;
    query << "DELETE FROM file_has_tags WHERE tags_id = " << t.get_id() << " AND files_id = " << f.get_id();
    
    errno = sqlite3_exec(db, query.str().c_str(), 0, 0, &err_msg);
    
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }

    return errno==SQLITE_OK?0:-1;
}

int Database::del(File& f) {
    char *err_msg = 0;
    errno = 0;
    set_errmsg("");
    
    if (!db) {
	errno = sqlite3_open(path.c_str(), &db);
    }
    
    if (errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }
    
    /*
    stringstream query;
    query << "DELETE FROM file_has_tags WHERE files_id = " << f.get_id();
    errno = sqlite3_exec(db, query.str().c_str(), 0, 0, &err_msg);
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	return errno==SQLITE_OK?0:-1;
    }
    */
    
    stringstream query2;
    query2 << "DELETE FROM files WHERE id = " << f.get_id();
    errno = sqlite3_exec(db, query2.str().c_str(), 0, 0, &err_msg);
    if (SQLITE_OK != errno) {
	set_errmsg(sqlite3_errmsg(db));
	sqlite3_close(db);
	db = 0;
	return errno==SQLITE_OK?0:-1;
    }

    stringstream ofile;
    ofile << this->files_dir << "/" << f.get_id();
    errno = unlink(ofile.str().c_str());

    if (0 != errno) {
	set_errmsg("nie udalo sie usunac pliku");
	return errno==SQLITE_OK?0:-1;
    }

    return errno==SQLITE_OK?0:-1;
}
